<template>
  <el-upload
      ref="uploadRef"
      class="upload-demo"
      action=""
      :auto-upload="false"
      :on-change="handleChange"
  >
    <template #trigger>
      <el-button type="primary">选择文件</el-button>
    </template>
    <el-button @click="handleUpload">上传</el-button>
  </el-upload>
</template>

<script setup lang="ts">
import {ref} from "vue";
import SparkMD5 from "spark-md5";

const chunkSize = 1024 * 1024; // 1MB
const fileName = ref("");
const fileHash = ref("");

const fileContent = ref(null);

const handleChange = (file) => {
  fileContent.value = file.raw;
};

// 创建文件
const createChunks = (file: File) => {
  let start = 0;
  const chunks = [];
  while (start < file.size) {
    chunks.push(file.slice(start, start + chunkSize));
    start += chunkSize;
  }
  return chunks;
};

// hash值
const calculateHash = (file: File): Promise<string> => {
  return new Promise((resolve) => {
    const fileReader = new FileReader();
    fileReader.readAsArrayBuffer(file);
    fileReader.onload = function (e) {
      const spark = new SparkMD5.ArrayBuffer();
      spark.append((e.target as FileReader).result as ArrayBuffer);
      resolve(spark.end());
    };
  });
};

// 控制请求并发
const concurRequest = (
    taskPool: Array<() => Promise<Response>>,
    max: number
): Promise<Array<Response | unknown>> => {
  return new Promise((resolve) => {
    if (taskPool.length === 0) {
      resolve([]);
      return;
    }

    const results: Array<Response | unknown> = [];
    let index = 0;
    let count = 0;

    const request = async () => {
      if (index === taskPool.length) return;
      const i = index;
      const task = taskPool[index];
      index++;
      try {
        results[i] = await task();
      } catch (err) {
        results[i] = err;
      } finally {
        count++;
        if (count === taskPool.length) {
          resolve(results);
        }
        request();
      }
    };

    const times = Math.min(max, taskPool.length);
    for (let i = 0; i < times; i++) {
      request();
    }
  });
};

// 合并分片请求
const mergeRequest = () => {
  fetch("http://localhost:3000/merge", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      fileHash: fileHash.value,
      fileName: fileName.value,
    }),
  });
};

// 上传文件分片
const uploadChunks = async (
    chunks: Array<Blob>,
    existChunks: Array<string>
) => {
  const formDatas = chunks
      .map((chunk, index) => ({
        fileHash: fileHash.value,
        chunkHash: fileHash.value + "-" + index,
        chunk,
      }))
      .filter((item) => !existChunks.includes(item.chunkHash))
      .map((item) => {
        const formData = new FormData();
        formData.append("fileHash", item.fileHash);
        formData.append("chunkHash", item.chunkHash);
        formData.append("chunk", item.chunk);
        return formData;
      });

  const taskPool = formDatas.map(
      (formData) => () =>
          fetch("http://localhost:3000/upload", {
            method: "POST",
            body: formData,
          })
  );

  // 控制请求并发
  await concurRequest(taskPool, 6);

  // 合并分片请求
  mergeRequest();
};

// 校验文件、文件分片是否存在
const verify = (fileHash: string, fileName: string) => {
  return fetch("http://localhost:3000/verify", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      fileHash,
      fileName,
    }),
  }).then((res) => res.json());
};

// 绑定上传事件
const handleUpload = async () => {
  const fileData = fileContent.value
  if (!fileData) return;

  fileName.value = fileData.name;

  // 创建文件分片
  const chunks = createChunks(fileData);

  // 计算文件内容hash值
  fileHash.value = await calculateHash(fileData);

  // 校验文件、文件分片是否存在
  const verifyRes = await verify(fileHash.value, fileName.value);
  const {existFile, existChunks} = verifyRes.data;
  if (existFile) return;

  // 上传文件分片
  uploadChunks(chunks, existChunks);
};
</script>



